﻿using Apewer;
using System;
using System.Collections.Generic;
using System.Text;

namespace Apewer.Internals
{

    internal class UrlEncoding
    {

        public static string Encode(string plain)
        {
            return TextUtility.FromBytes(Encode(TextUtility.Bytes(plain)));
        }

        public static string Decode(string escaped)
        {
            return Decode(escaped, Encoding.UTF8);
        }

        private UrlEncoding() { }

        #region encode static

        /// <summary>对 URL 编码。</summary>
        static byte[] Encode(byte[] bytes)
        {
            if (bytes == null) return null;
            if (bytes.Length < 1) return BytesUtility.Empty;

            var offset = 0;
            var count = bytes.Length;

            int num = 0;
            int num2 = 0;
            for (int i = 0; i < count; i++)
            {
                char c = (char)bytes[offset + i];
                if (c == ' ')
                {
                    num++;
                }
                else if (!IsUrlSafeChar(c))
                {
                    num2++;
                }
            }
            if (num == 0 && num2 == 0)
            {
                if (offset == 0 && bytes.Length == count)
                {
                    return bytes;
                }
                byte[] array = new byte[count];
                Buffer.BlockCopy(bytes, offset, array, 0, count);
                return array;
            }
            byte[] array2 = new byte[count + num2 * 2];
            int num3 = 0;
            for (int j = 0; j < count; j++)
            {
                byte b = bytes[offset + j];
                char c2 = (char)b;
                if (IsUrlSafeChar(c2))
                {
                    array2[num3++] = b;
                }
                else if (c2 == ' ')
                {
                    array2[num3++] = 43;
                }
                else
                {
                    array2[num3++] = 37;
                    array2[num3++] = (byte)IntToHex(b >> 4 & 0xF);
                    array2[num3++] = (byte)IntToHex(b & 0xF);
                }
            }
            return array2;
        }

        static bool IsUrlSafeChar(char ch)
        {
            //const string lower = "abcdefghijklmnopqrstuvwxyz";
            //const string upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            //const string numbers = "0123456789";
            //const string symbols = "!()*-._";

            if (ch >= 'a' && ch <= 'z') return true;
            if (ch >= 'A' && ch <= 'Z') return true;
            if (ch >= '0' && ch <= '9') return true;
            switch (ch)
            {
                case '!':
                case '(':
                case ')':
                case '*':
                case '-':
                case '.':
                case '_':
                    return true;
                default:
                    return false;
            }
        }

        static char IntToHex(int n)
        {
            if (n <= 9)
            {
                return (char)(n + 48);
            }
            return (char)(n - 10 + 97);
        }

        #endregion

        #region decode instance

        private int _bufferSize;

        private int _numChars;

        private char[] _charBuffer;

        private int _numBytes;

        private byte[] _byteBuffer;

        private Encoding _encoding;

        private void FlushBytes()
        {
            if (_numBytes > 0)
            {
                _numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars);
                _numBytes = 0;
            }
        }

        internal UrlEncoding(int bufferSize, Encoding encoding)
        {
            _bufferSize = bufferSize;
            _encoding = encoding;
            _charBuffer = new char[bufferSize];
        }

        internal void AddChar(char ch)
        {
            if (_numBytes > 0)
            {
                FlushBytes();
            }
            _charBuffer[_numChars++] = ch;
        }

        internal void AddByte(byte b)
        {
            if (_byteBuffer == null)
            {
                _byteBuffer = new byte[_bufferSize];
            }
            _byteBuffer[_numBytes++] = b;
        }

        internal string GetString()
        {
            if (_numBytes > 0)
            {
                FlushBytes();
            }
            if (_numChars > 0)
            {
                return new string(_charBuffer, 0, _numChars);
            }
            return string.Empty;
        }

        #endregion

        #region decode static

        static string Decode(string value, Encoding encoding)
        {
            if (value == null)
            {
                return null;
            }
            int length = value.Length;
            var urlDecoder = new UrlEncoding(length, encoding);
            for (int i = 0; i < length; i++)
            {
                char c = value[i];
                switch (c)
                {
                    case '+':
                        c = ' ';
                        goto default;
                    case '%':
                        if (i < length - 2)
                        {
                            if (value[i + 1] == 'u' && i < length - 5)
                            {
                                int num = HexToInt(value[i + 2]);
                                int num2 = HexToInt(value[i + 3]);
                                int num3 = HexToInt(value[i + 4]);
                                int num4 = HexToInt(value[i + 5]);
                                if (num >= 0 && num2 >= 0 && num3 >= 0 && num4 >= 0)
                                {
                                    c = (char)(num << 12 | num2 << 8 | num3 << 4 | num4);
                                    i += 5;
                                    urlDecoder.AddChar(c);
                                    break;
                                }
                            }
                            else
                            {
                                int num5 = HexToInt(value[i + 1]);
                                int num6 = HexToInt(value[i + 2]);
                                if (num5 >= 0 && num6 >= 0)
                                {
                                    byte b = (byte)(num5 << 4 | num6);
                                    i += 2;
                                    urlDecoder.AddByte(b);
                                    break;
                                }
                            }
                        }
                        goto default;
                    default:
                        if ((c & 0xFF80) == 0)
                        {
                            urlDecoder.AddByte((byte)c);
                        }
                        else
                        {
                            urlDecoder.AddChar(c);
                        }
                        break;
                }
            }
            return ValidateString(urlDecoder.GetString());
        }

        static int HexToInt(char h)
        {
            if (h >= '0' && h <= '9')
            {
                return h - 48;
            }
            if (h >= 'a' && h <= 'f')
            {
                return h - 97 + 10;
            }
            if (h >= 'A' && h <= 'F')
            {
                return h - 65 + 10;
            }
            return -1;
        }

        static string ValidateString(string input)
        {
            var skipUtf16Validation = true;

            if (!skipUtf16Validation && !string.IsNullOrEmpty(input))
            {
                int num = -1;
                int num2 = 0;
                while (num2 < input.Length)
                {
                    if (!char.IsSurrogate(input[num2]))
                    {
                        num2++;
                        continue;
                    }
                    num = num2;
                    break;
                }
                if (num < 0)
                {
                    return input;
                }
                char[] array = input.ToCharArray();
                for (int i = num; i < array.Length; i++)
                {
                    char c = array[i];
                    if (char.IsLowSurrogate(c))
                    {
                        array[i] = '�';
                    }
                    else if (char.IsHighSurrogate(c))
                    {
                        if (i + 1 < array.Length && char.IsLowSurrogate(array[i + 1]))
                        {
                            i++;
                        }
                        else
                        {
                            array[i] = '�';
                        }
                    }
                }
                return new string(array);
            }
            return input;
        }

        #endregion

    }

}
